package cn.edu.zzuli.nothinglink.service.impl;

import cn.edu.zzuli.nothinglink.constant.WorkConst;
import cn.edu.zzuli.nothinglink.entity.*;
import cn.edu.zzuli.nothinglink.mapper.*;
import cn.edu.zzuli.nothinglink.service.WorkService;
import cn.edu.zzuli.nothinglink.vo.CheckItemVo;
import cn.edu.zzuli.nothinglink.vo.WorkInfoVo;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author suzyyy
 * @since 2021-04-22
 */
@Service
public class WorkServiceImpl extends ServiceImpl<WorkMapper, Work> implements WorkService {

    @Autowired
    private DataSourceTransactionManager txManager;

    @Resource
    StudentsMapper studentsMapper;

    @Resource
    CourseMapper courseMapper;

    @Resource
    WorkMapper workMapper;

    @Resource
    WorkSubmitRecordMapper workSubmitRecordMapper;

    @Resource
    ClassesMapper classesMapper;

    @Override
    public R createWork(WorkInfoVo vo, Integer uId) {

        Work w = new Work();
        w.setWorkTeaId(uId);
        setWorkInfo(vo, w);

        try {
            w.setStartTime(LocalDateTime.now());
            if (save(w)) return R.success();
            else return R.error();
        }catch (DataIntegrityViolationException e) {
            //现在不会了，因为改成json格式了，懒得改了。放这吧。
//            System.out.println(e);
            return R.error().add("error", "插入数据库异常，可能文本太长了...");
        }

    }

    private void setWorkInfo(WorkInfoVo vo, Work w) {
        //基本属性复制到 work 中
        BeanUtils.copyProperties(vo, w);
        Course course = courseMapper.selectById(w.getCourseId());
        w.setCourseName(course.getCourseName());

        //现在组装 checkInfos
        //注意！ vo中的checkInfos和 Work 中checkInfos 不一致
        // vo中是每个选项的信息！
        //先判断为选择题，如果是文本题，不进入判断，直接存入到 db 中
        if (vo.getWorkType() == WorkConst.TYPE_CHECK.Value()) {
            List<JSONObject> checkItem = vo.getCheckInfos();
            System.out.println("isMuitlSelect:::" + vo.isMuitlSelect());
            //判断是否为多选
            if (!vo.isMuitlSelect()) {
                System.out.println("单项选择题");
                //不为多选的时候，由于我们已经将 trueCheck 组装到 checkItem 中了，
                //所以不需要再设置正确选项
                JSONObject checkInfos = new JSONObject();
                checkInfos.put("isMuitlSelect", false);
                checkInfos.put("checkItem", checkItem);

                w.setCheckInfos(checkInfos.toJSONString());
            }else {
                System.out.println("多项选择题");
                JSONObject checkInfos = new JSONObject();
                //现在为多选了，那么我们必选筛选出 正确的多个选项！
                filterTheTrueCheck(vo.getCheckList(), vo.getCheckInfos());
                checkInfos.put("checkItem", vo.getCheckInfos());
                checkInfos.put("isMuitlSelect", true);
                if (vo.isStartHalfRightGetScore()){
                    System.out.println("开启了半对得分");
                    checkInfos.put("isStartHalfRightGetScore", true);
                }
                else checkInfos.put("isStartHalfRightGetScore", false);

                w.setCheckInfos(checkInfos.toJSONString());

            }
        }

        //判断题干类型是不是图片类型，如果是文本类型转为json
        //(因为我不知道设置text，还是更大的文本类型更合适，干脆把字段设置json类型了)
        if (vo.getContentType() == WorkConst.CONTENT_TEXT.Value()) {
            w.setWorkImgUrl(null);
            JSONObject content = new JSONObject();
            content.put("content", vo.getContent());
            w.setContent(content.toJSONString());
        }else {
            w.setContent(null);
        }

        System.out.println(w);
    }

    @Override
    public R getWorksInTheCourse(Integer courseId) {
        LocalDateTime now = LocalDateTime.now();
        List<Work> works = list(
                new LambdaQueryWrapper<Work>()
                .eq(Work::getCourseId, courseId)
//                .ge(Work::getEndTime, now)//结束时间大于当前时间
                .orderByAsc(Work::getEnd)//按照end升序，即0->1,未结束的作业在前。
                .orderByAsc(Work::getEndTime)//按照时间升序，即时间结束日期较近的作业在前
        );

        //过滤出过期的作业，然后修改数据库
        return R.success().add("works", FilterTheEndWork2DbAndReturnNewWork(works, now));
    }

    @Override
    public Work getWorkInfoById(Integer workId) {
        Work w = null;
        if ( (w = getById(workId)) != null) {
            //处理一下
            //如果是文本类型，我们最好将json取出来直接穿给前端
            if (w.getContentType() == WorkConst.CONTENT_TEXT.Value()) {
                w.setWorkImgUrl(null);
                JSONObject content_json = JSONObject.parseObject(w.getContent(), JSONObject.class);
                w.setContent(content_json.getString("content"));
            }else {
                w.setContent(null);
            }

            return w;
        }

        return null;
    }

    @Override
    public R updateTheWork(WorkInfoVo vo) {
        Work w = new Work();
        setWorkInfo(vo, w);
        if (updateById(w)) return R.success();
        return R.error();
    }

    @Override
    public R getWorksInMyClass(Integer stuId, String type) {
        //首先获取该学生的信息。
        Students s = null;
        //判断学生是否已经绑定了班级。
        if ((s = studentsMapper.selectById(stuId)) != null
                && s.getBelongClassId() != null) {
            //查看查询的 条件
            //type = uncommited 时，查询未提交。默认也是这种
            //type = commited 已提交
            //type = all 查询全部
            //type = timeout 查询已经过期的。

            //获取该班级绑定的课程
            LocalDateTime now = LocalDateTime.now();
            List<Integer> courseIds = courseMapper.getMyCoursesIdInTheClass(s.getBelongClassId(), now);

            if (courseIds.size() == 0) return R.error().add("error", "班级暂无绑定课程");
            //获取全部作业
            List<Work> courseWorksInList = workMapper.getCourseWorksInList(courseIds);

            if (courseWorksInList.size() == 0) return R.error().add("error", "暂无作业发布");
            courseWorksInList.forEach(System.out::println);

            //比对所有作业的日期是否过期
            CompletableFuture<List<Work>> formatWorkTask = CompletableFuture.supplyAsync(() -> {
                return formatWork(courseWorksInList, now);
            });

            List<Integer> workIds = courseWorksInList.stream().map(Work::getWorkId)
                    .collect(Collectors.toList());

            //查询出来已经提交的workId
            List<Integer> workIdsByInfo = workSubmitRecordMapper.getWorkIdsByInfo(workIds, stuId);
            Map<Integer, Integer> submitWorkIdMap = workIdsByInfo.stream().collect(Collectors.toMap(k -> k, v -> v));

            double finishRating = 0.0;
            System.out.println(workIdsByInfo.size());
            System.out.println(courseWorksInList.size());
            if (workIdsByInfo.size() != 0) finishRating = workIdsByInfo.size() * 1.0 / courseWorksInList.size();
            try {
                List<Work> works_sorted = formatWorkTask.get();

                //查询对应 courseIds 里的全部作业。
                switch (type) {
                    case "uncommited":
                        if (works_sorted.size() != 0) {
                            works_sorted = works_sorted.stream()
                                    .filter(w -> !submitWorkIdMap.containsKey(w.getWorkId()))
                                    .collect(Collectors.toList());
                        }
                        break;
                    case "commited":
                        if (works_sorted.size() != 0) {
                            works_sorted = works_sorted.stream()
                                    .filter(w -> submitWorkIdMap.containsKey(w.getWorkId()))
                                    .collect(Collectors.toList());
                        }
                        break;
                    case "timeout":
                        works_sorted = filterTimeoutWork(works_sorted);
                        break;
                    default:
                        //默认去查询all
                        break;
                }

                return R.success().add("works", works_sorted).add("finishRating", finishRating);

            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
                return R.error().add("error", "服务器出错啦");
            }
        }
        return R.error().add("error", "请先绑定班级! :)");
    }


    public R getWorksInCourseByCourseName(String courseName) {
        LocalDateTime now = LocalDateTime.now();
        Course c = courseMapper.selectOne(new LambdaQueryWrapper<Course>().eq(Course::getCourseName, courseName));
        if (c == null) return R.error().add("error", "该课程不存在，请检查课程名");


        List<Work> works = list(
                new LambdaQueryWrapper<Work>()
                        .eq(Work::getCourseId, c.getCourseId())
                        .orderByAsc(Work::getEnd)//按照end升序，即0->1,未结束的作业在前。
                        .orderByAsc(Work::getEndTime)//按照时间升序，即时间结束日期较近的作业在前
        );

        List<Work> work_format = formatWork(works, now);
        return R.success().add("works", work_format);
    }

    @Override
    public R submitWorkRecord(Integer stuId, JSONObject json) {
        Students s = studentsMapper.selectById(stuId);
        //查询该作业
        Work w = getById(json.getInteger("workId"));
        if (w == null) return R.error().add("error", "该作业不存在。");
        //判断作业是否过期
        if (w.getEnd() == 1
                || !LocalDateTime.now().isBefore(w.getEndTime()))
            return R.error().add("error", "作业已经过期了");

        //判断是否提交过了
        WorkSubmitRecord submitRecord = workSubmitRecordMapper.selectOne(
                new LambdaQueryWrapper<WorkSubmitRecord>()
                        .eq(WorkSubmitRecord::getWorkId, w.getWorkId())
                        .eq(WorkSubmitRecord::getWsStuId, stuId)
        );
        if (submitRecord != null) return R.error().add("error", "提交过了哦，请勿重复提交");

        WorkSubmitRecord record = new WorkSubmitRecord();
        record.setWsStuId(stuId).setWsStuName(s.getStuName())
                .setClassId(s.getBelongClassId())
                .setStuNumber(s.getStuNumber())
                .setWorkId(w.getWorkId()).setSubmitTime(LocalDateTime.now());

        //没有提交再进行判断。
        //如果是选择题，进行判定。
        if (WorkConst.TYPE_CHECK.Value().equals(w.getWorkType())) {
            record.setWorkType(WorkConst.TYPE_CHECK.Value());
            String info = "答错了... 呢 :0";

            //如果是选择题的话。
            //{"checkItem": [{"label": "A", "content": "是关系型数据库?", "isTrueCheck": true}, {"label": "B", "content": "是非关系型数据库?", "isTrueCheck": false}], "isMuitlSelect": false}
            JSONObject checkInfos = JSONObject.parseObject(w.getCheckInfos(), JSONObject.class);
            JSONArray checkItem = checkInfos.getJSONArray("checkItem");
            List<CheckItemVo> checkItemVos = checkItem.toJavaList(CheckItemVo.class);

            //判断是否为多选。
            Boolean isMuitlSelect = checkInfos.getBoolean("isMuitlSelect");

            if (!isMuitlSelect) {
                String checkLabel = json.getString("check");
                if (checkLabel == null) return R.error().add("error", "选择题缺少选项");

                String trueLabel = null;
                //非多选的情况下，不需要考虑这么多，只需要判断是否正确就行了。
                for (CheckItemVo c : checkItemVos) {
                    if (c.isTrueCheck()) {
                        trueLabel = c.getLabel();
                        break;
                    }
                }

                if (trueLabel.equals(checkLabel)) {
                    //此时说明选择正确了。
                    record.setScore(w.getWorkScore());
                    info = "答对啦，:) 恭喜哦。拿到了" + record.getScore();
                }

                JSONObject checkInfo = new JSONObject();
                checkInfo.put("check", checkLabel);
                record.setWsText(checkInfo.toJSONString());
            }else {
                //checkList
                JSONArray checkList = json.getJSONArray("checkList");
                if (checkList == null) return R.error().add("error", "选择题缺少选项");

                //如果是多选题的话，我们还要判断是否开启了半对得分。
                //{"checkItem": [{"label": "A", "content": "选我对", "isTrueCheck": true}, {"label": "B", "content": "选我不对", "isTrueCheck": false}, {"label": "C", "content": "选我对", "isTrueCheck": true}, {"label": "D", "content": "选我不对", "isTrueCheck": false}], "isMuitlSelect": true, "isStartHalfRightGetScore": true}
                boolean isStartHalfRightGetScore = checkInfos.getBoolean("isStartHalfRightGetScore");
                HashMap<String, Boolean> trueCheckMap = new HashMap<>();
                //挑选出来所有正确的选项。
                for (CheckItemVo c : checkItemVos) {
                    if (c.isTrueCheck()) {
                        trueCheckMap.put(c.getLabel(), true);
                    }
                }

                //判断选项正确。我们要计算对了几个，正确选项有几个
                //然后我们这里计算简单一点,防止有小数不好看，只要如果不全对，直接 score/2
                int count = 0;
                for (Object check : checkList) {
                    if (trueCheckMap.containsKey(check)) {
                        count++;
                    }
                }
                if (count == trueCheckMap.size()
                        && checkList.size() == trueCheckMap.size()) {
                    record.setScore(w.getWorkScore());
                    info = "全答对啦，:) 恭喜哦。拿到了" + record.getScore();
                }

                if (isStartHalfRightGetScore
                        && count > 0 && count < trueCheckMap.size()) {
                    //半对
                    record.setScore(w.getWorkScore() / 2.0);
                    info = "半对哦，:) 拿到了" + record.getScore();
                }

                JSONObject checkInfo = new JSONObject();
                checkInfo.put("checkList", checkList);
                record.setWsText(checkInfo.toJSONString());

            }


            //save
            workSubmitRecordMapper.insert(record);

            return R.success().add("info", info);

        }


        record.setWorkType(WorkConst.TYPE_TEXT.Value());

        //如果不是选择题，则判断提交类型后直接取出json数据，然后直接save
        if (json.getInteger("contentType") == WorkConst.CONTENT_TEXT.Value()){
            String content = json.getString("content");
            JSONObject ws_text = new JSONObject();
            record.setWsType(WorkConst.CONTENT_TEXT.Value());
            ws_text.put("content", content);
            record.setWsText(ws_text.toJSONString());
        }else {
            record.setWsType(WorkConst.CONTENT_IMG.Value());
            String workImgUrl = json.getString("workImgUrl");
            record.setWsFileUrl(workImgUrl);
        }

        //添加一条提交记录
        workSubmitRecordMapper.insert(record);

        return R.success().add("info", "提交成功");
    }

    @Override
    public R getWorkSubmitRecordByWorkId(Integer workId) {
        Work work = workMapper.selectById(workId);
        if (work.getWorkType().equals(WorkConst.TYPE_CHECK.Value())) return R.error().add("error", "选择题不用查看啊:)");

        //获取该作业全部的提交记录
        List<WorkSubmitRecord> records = workSubmitRecordMapper.selectList(
                new LambdaQueryWrapper<WorkSubmitRecord>().eq(WorkSubmitRecord::getWorkId, workId)
        );

        records.parallelStream().forEach(r -> {
            if (r.getWsType().equals(WorkConst.CONTENT_TEXT.Value())) {
                String wsText = r.getWsText();
                JSONObject content = JSONObject.parseObject(wsText, JSONObject.class);
                r.setWsText(content.getString("content"));
            }
            //查询班级信息
            String className = classesMapper.selectOne(
                    new LambdaQueryWrapper<Classes>().select(Classes::getClassRealName).eq(Classes::getClassId, r.getClassId())
            ).getClassRealName();
            r.setClassName(className);
        });
        return R.success().add("records", records);
    }

    public List<Work> formatWork(List<Work>  works, LocalDateTime now) {
        works.parallelStream().forEach(w -> {

            //如果是选择题类型，判断是不是多选
            if (w.getWorkType() == WorkConst.TYPE_CHECK.Value()) {
                JSONObject checkInfos = JSONObject.parseObject(w.getCheckInfos(), JSONObject.class);
                JSONArray checkItem = checkInfos.getJSONArray("checkItem");
                checkItem.forEach(c -> {
                    JSONObject jsonObject = (JSONObject) c;
                    jsonObject.remove("isTrueCheck");
                });
                checkInfos.put("checkItem", checkItem);
                w.setCheckInfos(checkInfos.toJSONString());
                System.out.println(w);

            }

            if (w.getContentType() == WorkConst.CONTENT_TEXT.Value()) {
                w.setWorkImgUrl("");
                JSONObject jsonObject = JSONObject.parseObject(w.getContent());
                w.setContent(jsonObject.getString("content"));
            }

            if (w.getContentType() == WorkConst.CONTENT_IMG.Value()) {
                w.setContent("");
            }

            if (!now.isBefore(w.getEndTime())
                    && w.getEnd() == 0) {
                //此时应该已经结束了。
                w.setEnd(1);
            }

        });

        //重新排列一下。
        List<Work> works_sorted = works.stream()
                .sorted((w1, w2) -> w1.getEnd() - w2.getEnd())
                .collect(Collectors.toList());

        return works_sorted;
    }

    public List<Work> filterTimeoutWork(List<Work>  works) {
        return works.stream()
                .filter(w -> w.getEnd() == 1)
                .collect(Collectors.toList());
    }

    /**
     * 有一些日期已经小于当前时间的数据，把数据库的end标志位置位1,
     * 所以我们要过滤出来过期的work，然后修改数据库
     * @param works
     * @param now
     */
    List<Work> FilterTheEndWork2DbAndReturnNewWork(List<Work> works, LocalDateTime now) {
        CopyOnWriteArrayList<Work> end_workId = new CopyOnWriteArrayList<>();
        works.parallelStream().forEach(w -> {
            //如果是选择题类型，判断是不是多选
            if (w.getWorkType() == WorkConst.TYPE_CHECK.Value()) {
                JSONObject checkInfos = JSONObject.parseObject(w.getCheckInfos(), JSONObject.class);
                if (checkInfos.getBoolean("isMuitlSelect")) {
                    //开启多选
                    w.setMuitlSelect(true);
                }
            }
            //不管是不是文本，这个接口不显示具体的内容。
            w.setContent(null);
            if (!now.isBefore(w.getEndTime())
                    && w.getEnd() == 0) {
                //此时应该已经结束了。
                end_workId.add(
                        new Work().setWorkId(w.getWorkId())
                                .setEnd(1)
                );
                w.setEnd(1);
            }

            //获取提交人数
            Integer count = workSubmitRecordMapper.selectCount(
                    new LambdaQueryWrapper<WorkSubmitRecord>().eq(WorkSubmitRecord::getWorkId, w.getWorkId())
            );
            w.setSubmitCount(count);
        });

        //如果主线程报错，也可以将异步线程内的事务回滚
        //这里做下测试，思路：
        //1- 主线程创建异步线程的事务
        //2- 主线程异常，同时回滚异步线程的事务
        //设想好像可以成立，哈哈哈
        //异步线程的事务
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();

        if (end_workId.size() != 0) {
            List<Work> works_sorted = works.stream()
                    .sorted((w1, w2) -> w1.getEnd() - w2.getEnd())
                    .collect(Collectors.toList());

            // 事物隔离级别，开启新事务
            def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);


            CompletableFuture.runAsync(() -> {
                TransactionStatus status = txManager.getTransaction(def);
                try {
                    this.updateBatchById(end_workId);
                    txManager.commit(status);
                }catch (Exception e) {
                    txManager.rollback(status);
                    return;
                }
            });

            return works_sorted;
        }

        return works;
    }



    private void filterTheTrueCheck(List<String> checkList, List<JSONObject> checkInfos) {
        Map<String, String> trueCheckMap
                = checkList.stream().collect(Collectors.toMap(s -> s, s -> s));
        checkInfos.stream().forEach(jsonObject -> {
            if (trueCheckMap
                    .containsKey(jsonObject.getString("label"))) {
                jsonObject.put("isTrueCheck", true);
            }else {
                jsonObject.put("isTrueCheck", false);
            }
        });

    }



}
